package org.sifappscanplugin.publisher; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.auth.AuthSchemeProvider; import org.apache.http.auth.AuthScope; import org.apache.http.auth.NTCredentials; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.ServiceUnavailableRetryStrategy; import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.entity.StringEntity; import org.apache.http.impl.auth.BasicSchemeFactory; import org.apache.http.impl.auth.DigestSchemeFactory; import org.apache.http.impl.auth.KerberosSchemeFactory; import org.apache.http.impl.auth.SPNegoSchemeFactory; import org.apache.http.impl.client.AutoRetryHttpClient; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.LaxRedirectStrategy; import org.apache.http.impl.conn.DefaultProxyRoutePlanner; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; //import org.sifappscanplugin.publisher.ASEAuthenticator; //import org.sif.core.authentication.Authenticator; import org.sif.core.authentication.JCIFSNTLMSchemeFactory; import org.sif.core.util.TextUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class connects to an AppScan Enterprise (ASE) instance to perform * various functions that are not supported by the AppScan Source for Security * Edition (ASSE) CLI. It relies on the AppScan REST services as well as the web * interface. NTLM and Jazz authentication are supported. NTLMv2 is even * supported, even though ASSE itself does not currently support it. This class * has been tested with ASE 8.6. * * @author David Anderson * @date February, 2013 * */ public class ASERestServicesClient { final Logger logger = LoggerFactory.getLogger( ASERestServicesClient.class ); PrintWriter writer; CloseableHttpClient httpClient; HttpContext httpContext; // Authenticator authenticator; String location; XPath xpath; String aseUri = "appscanreporting.enterprise.irs.gov/ase"; boolean loggingInit = false; private String domain; private String user; private String password; private boolean acceptSsl; private final static int FOLDER_ID_ROOT = 1; static boolean testMode = false; // Reads XML files from local file-system // rather than from an ASE server. private void setupLogging() { try { writer = new PrintWriter( "C:\\Plugin_Log.log", "UTF-8" ); loggingInit = true; writer.println(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // public ASERestServicesClient() // { // // Initialization of XPath utilities // XPathFactory factory = XPathFactory.newInstance(); // xpath = factory.newXPath(); // xpath.setNamespaceContext( _nsContext ); // // if ( !loggingInit ) // { // // Disabled to diagnose errors with 9.0.1 // // setupLogging(); // } // // } public ASERestServicesClient(String location, String domain, String user, String password, boolean acceptSsl) { // Initialization of XPath utilities XPathFactory factory = XPathFactory.newInstance(); xpath = factory.newXPath(); xpath.setNamespaceContext( _nsContext ); // authenticator = new ASEAuthenticator( location, domain, user, password, acceptSsl ); // httpClient = authenticator.getHttpClient(); // this.location = location; this.location = location; this.domain = domain; this.user = user; this.password = password; this.acceptSsl = acceptSsl; } // public ASERestServicesClient(String location) // { // this(); // this.location = location; // } /** * Establish an authenticated HTTP connection to the location using Jazz authentication, which * is a simple POST-based userid+password authentication scheme implemented by Jazz Team Server. * This method implements preemptive authentication using the provided credentials, which is not * a best practice for server to server communication. Properly managed client certificates * should be used instead. * FIXME: I'm not sure about the above, and it may have changed in more recent versions of ASE. * We should document here the sequence of messages expected for a successful authentication. */ public void login() throws ClientProtocolException, IOException { // POST to /services/login // Content-Type: application/x-www-form-urlencoded // userid: element containing the user's login id. // password: element containing the user's password. // Create a local instance of cookie store CookieStore cookieStore = new BasicCookieStore(); httpContext = new BasicHttpContext(); try { // We need the HTTP client to automatically follow redirects for POSTs since ASE will // routinely respond with 302 for a browser compatibility check. HttpClientBuilder builder = HttpClients.custom(); // builder.setRedirectStrategy(new LaxRedirectStrategy() // { // @Override // public boolean isRedirected(HttpRequest request, // HttpResponse response, // HttpContext context) throws ProtocolException // { // logger.debug( "Redirecting request: " + request ); // return super.isRedirected( request, response, context ); // } // }); builder.setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryStrategy() { @Override public boolean retryRequest( final HttpResponse response, final int executionCount, final HttpContext context) { logger.debug( "Checking for retry condition" ); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 302 && executionCount < 2) logger.error( "Retrying request due to 302 redirect response" ); return statusCode == 302 && executionCount < 2; } @Override public long getRetryInterval() { return 0; } }); // HttpHost proxy = new HttpHost("localhost", 5555); // DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); // builder.setRoutePlanner( routePlanner ); if ( isAcceptSsl() ) { logger.debug( "Initializing https client that ignores SSL certificate mismatches" ); Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create() .register(AuthSchemes.NTLM, new JCIFSNTLMSchemeFactory()) .register(AuthSchemes.BASIC, new BasicSchemeFactory()) .register(AuthSchemes.DIGEST, new DigestSchemeFactory()) .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory()) .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory()) .build(); builder.setSSLSocketFactory(getSSLSocketFactoryTrustingAllCerts()); builder.setDefaultAuthSchemeRegistry(authSchemeRegistry); builder.setDefaultCookieStore(cookieStore); httpClient = builder.build(); } else { logger.debug( "Initializing default https client" ); httpClient = builder.build(); } } catch (KeyManagementException e) { logger.error( "Key management", e ); } catch (NoSuchAlgorithmException e) { logger.error( "No such algorithm", e ); } catch (KeyStoreException e) { logger.error( "Problem with keystore", e ); } // String localHostname; // try // { // localHostname = InetAddress.getLocalHost().getCanonicalHostName(); // } // catch (UnknownHostException e1) // { // logger.error("Cannot get local hostname", e1); // localHostname = "localhost"; // } CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials( AuthScope.ANY, new NTCredentials(getUser(), getPassword(), getLocation(), getDomain())); HttpClientContext context = HttpClientContext.create(); context.setCredentialsProvider(credentialsProvider); HttpPost request = new HttpPost( getLocation() + "/services/login" ); // Setup the request parameters List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>( 2 ); nameValuePairs.add( new BasicNameValuePair( "userid", getUser() ) ); nameValuePairs.add( new BasicNameValuePair( "password", getPassword() ) ); request.setEntity( new UrlEncodedFormEntity( nameValuePairs ) ); CloseableHttpResponse response = null; try { // Make the request // outStream.println("New HTTP Request: " + // request.getRequestLine()); response = send(request, httpContext); logger.debug( response.getStatusLine().toString() ); // outStream.println("Response: " + response.getStatusLine()); if ( response != null ) { HttpEntity responseEntity = response.getEntity(); logger.debug( response.getStatusLine().toString() ); if ( responseEntity != null ) { logger.debug( "Response content length: " + responseEntity.getContentLength() ); } String jsonResultString = EntityUtils.toString( responseEntity ); // logger.debug( "Response: " + jsonResultString ); EntityUtils.consume( responseEntity ); } } catch (ClientProtocolException e) { logger.error( "Illegal protocol", e ); } catch (IOException e) { logger.error( "Error sending request", e ); } finally { if ( response != null ) { response.close(); } } } /** * Get an SSL connection factory that trusts all certificates and allows hostname mismatches. * WARNING: This is NOT recommended for a production system as it exposes the application to * man in the middle attacks. In production, the following should be done: * * 1. The public key should be imported to the appropriate truststore on the client machine, * as in this example command: * * keytool -import -alias appscan -file mycert.cer -keystore cacertspersonal * * Refer to the documentation for your release of AppScan Source to locate the truststore. * * 2. The certificate should be created using the correct subjectAlternativeName * (see http://tools.ietf.org/search/rfc6125) so that it matches the host. * * For example: * * <pre> * keytool -genkeypair \ * -keystore keystore.jks \ * -dname "CN=example.com, OU=Sun Java System Application Server, O=Sun Microsystems, L=Santa Clara, ST=California, C=US" \ * -keypass changeit \ * -storepass changeit \ * -keyalg RSA \ * -keysize 2048 \ * -alias example \ * -ext SAN=DNS:example.com \ * -validity 9999 * </pre> * @return * @throws KeyManagementException * @throws NoSuchAlgorithmException * @throws KeyStoreException */ protected SSLConnectionSocketFactory getSSLSocketFactoryTrustingAllCerts() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { SSLConnectionSocketFactory factory = null; SSLContextBuilder builder = new SSLContextBuilder(); builder.loadTrustMaterial(null, new TrustSelfSignedStrategy()); // factory = new SSLConnectionSocketFactory(builder.build()); factory = new SSLConnectionSocketFactory(builder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); return factory; } public CloseableHttpResponse send(HttpUriRequest request, HttpContext httpContext) throws ClientProtocolException, IOException { return httpClient.execute( request, httpContext ); } /** * Get the folder id that corresponds to the given folder path on the ASE instance. * * @param folder * @return */ public Integer getFolderIdService(String folder) { Integer id = null; try { if (!testMode) { login(); } id = getFolderId(folder); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } // finally // { // if (!testMode) // { // authenticator.shutdown(); // } // } return id; } /** * Create a new folder at the given folder path on the ASE instance. * * @param folder * @return */ public boolean viewFolderService(String folder) { boolean result = false; try { if (!testMode) { login(); } result = viewFolder(folder); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (ASEClientException e) { e.printStackTrace(); } // finally // { // if (!testMode) // { // authenticator.shutdown(); // } // } return result; } /** * Create a new folder at the given folder path on the ASE instance. * * @param folder * @return */ public boolean createFolderService(String folder, String description, String contact) { boolean result = false; try { if (!testMode) { login(); } result = createFolder(folder, description, contact); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (ASEClientException e) { e.printStackTrace(); } // finally // { // if (!testMode) // { // authenticator.shutdown(); // } // } return result; } public String getLocation() { return location; } public String getDomain() { return domain; } public String getUser() { return user; } public String getPassword() { return password; } public boolean isAcceptSsl() { return acceptSsl; } /** * @deprecated * @param parentId * @param folderName * @return */ public int checkForFolderAtParentId(int parentId, String folderName) { // FIXME: Hack! int folderId = -99; // a return value of -99 means the folder was not // found String folderCheckURI = this.location + "/services/folders/" + parentId + "/folders"; logger.debug( "URL: " + folderCheckURI ); Document response = null; try { response = sendRESTRequest( folderCheckURI, "" ); } catch (ParserConfigurationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (SAXException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } if ( response != null ) { try { // logger.info("Response is not null."); checkForError( response ); } catch (ASEClientException e) { e.printStackTrace(); } } if ( response != null ) { try { Object folderResult = xpath.evaluate( "/ase:folder | /ase:folders/ase:folder", response, XPathConstants.NODESET ); NodeList folderNodes = (NodeList) folderResult; for ( int i = 0; i < folderNodes.getLength(); i++ ) { logger.debug( "Looking for folder in a new node..." ); Element folderNode = (Element) folderNodes.item( i ); if ( xpath.evaluate( "ase:name/text()", folderNode, XPathConstants.STRING ) .toString().equals( folderName ) ) { logger.debug( "Folder element name: " + folderName ); String folderIdString = (String) xpath.evaluate( "ase:id/text()", folderNode, XPathConstants.STRING ); logger.debug( "Folder element id: " + folderIdString ); folderId = Integer.parseInt( folderIdString ); logger.debug( "Found folder: " + folderName + "!" ); logger.debug( "Folder ID: " + folderId ); break; } } logger.debug( "Finished with that node.." ); logger.debug( "response start--------------" ); printDocument( response ); logger.debug( "response end----------------" ); } catch (XPathExpressionException e) { e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (TransformerException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return folderId; } /** * @deprecated * @param parentId * @param folder * @param description * @param contact * @throws ParserConfigurationException * @throws SAXException * @throws IOException * @throws ASEClientException */ public void createFolder(int parentId, String folder, String description, String contact) throws ParserConfigurationException, SAXException, IOException, ASEClientException { ASElogin(); logger.debug( "Sending Create Folder Post..." ); sendCreateFolderPost( folder, parentId, description, contact, 0 ); ASElogout(); } /** * @deprecated * @throws ClientProtocolException * @throws IOException */ private void ASElogin() throws ClientProtocolException, IOException { login(); logger.info( "Logged in." ); } /** * @deprecated */ private void ASElogout() { logger.info( "Logging out..." ); // authenticator.shutdown(); } /** * @deprecated * @param parentId * @param folders * @param description * @param contact * @return * @throws ParserConfigurationException * @throws SAXException * @throws IOException * @throws ASEClientException */ public int createFolderStructure(int parentId, String folders[], String description, String contact) throws ParserConfigurationException, SAXException, IOException, ASEClientException { logger.debug( "" ); logger.debug( "Called createFolderStructure..." ); int folderId = -99; try { ASElogin(); for ( int currentFolder = 0; currentFolder < folders.length; currentFolder++ ) { logger.debug( "" ); logger.debug( "createFolderService params: " + parentId + ", " + folders[currentFolder] + ", " + description + ", " + contact + "." ); // Check if the folder we are looking for exists at the // parentID. If not, -99 will be returned. folderId = checkForFolderAtParentId( parentId, folders[currentFolder] ); logger.debug( "Folder ID for " + folders[currentFolder] + " at parentId " + parentId + "is: " + folderId ); // Our folder was found if ( folderId != -99 ) { logger.debug( "Folder " + folders[currentFolder] + " was found with folderId " + folderId + " and will not be created." ); parentId = folderId; // Folder exists, so we keep moving.. continue; } else { ASElogout(); logger.debug( "Calling createFolder: " + parentId + ": " + folders[currentFolder] ); createFolder( parentId, folders[currentFolder], "", "" ); ASElogin(); logger.debug( "Confirming we actually created the folder.." ); folderId = checkForFolderAtParentId( parentId, folders[currentFolder] ); logger.debug( "FolderId found: " + folderId ); if ( folderId == -99 ) { // This should probably actually throw an exception, // since it means something didn't work logger.debug( "Folder was not actually created..." ); logger.debug( "" ); } else { logger.debug( "New Folder ID: " + folderId ); logger.debug( "Folder creation confirmed!" ); parentId = folderId; } } } } finally { // if (!testMode) // { ASElogout(); logger.debug( "" ); // outputStream.flush(); // outputStream.close(); // } } logger.debug( "Returning folderId: " + folderId ); return folderId; } private String translateJobFolderToASEFolder(String folder, ASERepository aseRepository) { String aseFolder = null; aseFolder = TextUtil.trimLeft( folder, "/" ); aseFolder = aseRepository.getRootFolder() + "/" + aseFolder; aseFolder.replaceAll( "\\\\", "/" ); aseFolder = TextUtil.trim( aseFolder, "/" ); return aseFolder; } Integer getFolderId(String folder) throws ParserConfigurationException, SAXException, IOException { Integer id = null; ASERepository aseRepository = new ASERepository(); mapFolders( this.location + "/services/folders", aseRepository, new Stack<String>() ); folder = translateJobFolderToASEFolder(folder, aseRepository); id = aseRepository.getMap().get( folder ); return id; } class ASERepository { private Map<String, Integer> map = new Hashtable<String, Integer>(); private String rootFolder; public int size() { return map.size(); } public boolean containsKey(Object key) { return map.containsKey( key ); } public Integer get(Object key) { return map.get( key ); } public Integer put(String key, Integer value) { return map.put( key, value ); } public Map<String, Integer> getMap() { return map; } public void setMap(Map<String, Integer> map) { this.map = map; } public String getRootFolder() { return rootFolder; } public void setRootFolder(String rootFolder) { this.rootFolder = rootFolder; } } ASERepository mapFolders(String rootFolderUrl, ASERepository aseRepository, Stack<String> pathElements) throws ParserConfigurationException, SAXException, IOException { logger.debug( "Mapping ASE folders structure at " + rootFolderUrl ); Document response = null; if ( testMode ) { DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); domFactory.setNamespaceAware( true ); // never forget this! File subfoldersFile = translateUrlToFilesystemPath( rootFolderUrl ); DocumentBuilder builder; builder = domFactory.newDocumentBuilder(); if ( subfoldersFile.exists() ) response = builder.parse( subfoldersFile ); else logger.debug( "WARNING: Missing node.xml at " + subfoldersFile ); } else { response = sendRESTRequest( rootFolderUrl, "" ); if ( response != null ) { try { checkForError( response ); } catch (ASEClientException e) { e.printStackTrace(); } } } if ( response != null ) { try { String folderName = null; Integer folderId = null; Object folderResult = xpath.evaluate( "/ase:folder | /ase:folders/ase:folder", response, XPathConstants.NODESET ); NodeList folderNodes = (NodeList) folderResult; for ( int i = 0; i < folderNodes.getLength(); i++ ) { Element folderNode = (Element) folderNodes.item( i ); folderName = (String) xpath.evaluate( "ase:name/text()", folderNode, XPathConstants.STRING ); // logger.debug( "Folder element name: " + folderName ); String folderIdString = (String) xpath.evaluate( "ase:id/text()", folderNode, XPathConstants.STRING ); // logger.debug( "Folder element id: " + folderIdString ); folderId = Integer.parseInt( folderIdString ); if ( folderName != null && folderId != null ) { if ( pathElements.size() >= 32 ) { logger.debug( "Stopping the mapping of a folder branch that exceeds the depth limit of 32." ); continue; } pathElements.push( folderName ); String path = TextUtil.convertListToString( pathElements, "/" ); // logger.debug( "#### Mapping folder path " + path + " with id " // + folderId ); aseRepository.put( path, folderId ); // Remember the root folder if (folderId == FOLDER_ID_ROOT) { aseRepository.setRootFolder( TextUtil.trim( path, "/") ); } String subfoldersUrl = (String) xpath.evaluate( "ase:folders/@href", folderNode, XPathConstants.STRING ); // Recurse into a sub-folder mapFolders( subfoldersUrl, aseRepository, pathElements ); pathElements.pop(); } } } catch (XPathExpressionException e) { e.printStackTrace(); } } return aseRepository; } private File translateUrlToFilesystemPath(String url) { logger.debug( "Translating URL " + url ); File file = null; int doubleSlashPos = url.indexOf( "//" ); int slashPos = url.indexOf( "/", doubleSlashPos + 2 ); // Skip the following "/ase/services/" String context = "/ase/services/"; if ( url.substring( slashPos, slashPos + context.length() ).equals( context ) ) file = new File( "test/" + url.substring( slashPos + context.length() ) + "/node.xml" ); else throw new RuntimeException( "Cannot find XML docuemnt for URL " + url + " at file: " + file ); return file; } /** * * @param service * - URL of the REST service call * @param data * - (optional) Content of the POST data (XML). When empty, using * GET. * @return Response (XML) * @throws ParserConfigurationException * @throws IOException * @throws SAXException * @throws Exception */ private Document sendRESTRequest(String service, String data) throws ParserConfigurationException, IOException, SAXException { logger.debug( "Sending REST request to " + service ); // outStream.println("Sending REST request to " + service); Document document = null; DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); domFactory.setNamespaceAware( true ); // never forget this! DocumentBuilder builder = domFactory.newDocumentBuilder(); HttpUriRequest request; if ( data != null && data.length() > 0 ) { HttpPost postRequest = new HttpPost( service ); postRequest.addHeader( "Content-Type", "application/x-www-form-urlencoded" ); StringEntity entity = new StringEntity( data ); postRequest.setEntity( entity ); request = postRequest; } else { request = new HttpGet( service ); } HttpResponse response = null; try { // Make the request response = send( request, httpContext ); // response = httpClient.execute( request, httpContext ); logger.debug( "REST response: " + response.getStatusLine() ); logger.debug( response.getStatusLine().toString() ); } catch (ClientProtocolException e) { logger.debug( "Illegal protocol" + e.getMessage() ); } catch (IOException e) { logger.debug( "Error sending request" + e.getMessage() ); } if ( response != null ) { HttpEntity responseEntity = response.getEntity(); logger.debug( response.getStatusLine().toString() ); if ( responseEntity != null ) { logger.debug( "Response content length: " + responseEntity.getContentLength() ); } document = builder.parse( responseEntity.getContent() ); } return document; } // FIXME // // This method does not seem to accurately return whether or not the folder // is created. // This method also does not accurately report if the path begins with ASE // FIXME: This method should be checked for logic issues public boolean createFolder(String folder, String description, String contact) throws ParserConfigurationException, SAXException, IOException, ASEClientException { logger.debug( "Creating ASE folder: " + folder ); boolean result = false; String parentFolder = null; if ( folder != null && folder.length() > 0 ) { ASERepository aseRepository = new ASERepository(); mapFolders( this.location + "/services/folders", aseRepository, new Stack<String>() ); folder = translateJobFolderToASEFolder(folder, aseRepository); String[] pathElements = folder.split( "/" ); if ( pathElements.length > 0 ) { logger.debug( "Creating ASE folder with path elements: " + Arrays.asList(pathElements) ); ArrayList<String> ancestorList = new ArrayList<String>(); // Create each ancestor directory path that is not in 'folders' for ( int i = 0; i < pathElements.length; i++ ) { String ancestorPathElement = pathElements[i]; parentFolder = TextUtil.convertListToString( ancestorList, "/" ); ancestorList.add( ancestorPathElement ); String thisFolder = TextUtil.convertListToString( ancestorList, "/" ); logger.debug( "Ensuring existence of folder " + thisFolder + " under " + parentFolder); // The root folder always exists, so we do not try to create it. if (i > 0) { // For each 'parentFolder' check if it is in // 'folders' Integer parentFolderId = aseRepository.get( parentFolder ); Integer thisFolderId = aseRepository.get( thisFolder ); if ( thisFolderId == null) { logger.debug( "Creating ASE folder under " + parentFolder + " with name " + ancestorPathElement ); // outStream.println("Creating ASE folder in " + // parentFolder + " with name " + // ancestorPathElement); sendCreateFolderPost( ancestorPathElement, parentFolderId.intValue(), description, contact, 0 ); // Add the folder we just created to our map of all folders. // FIXME: Maybe we should get the folder id back from the // sendCreateFolderPost() call so we can just add it to the map. mapFolders( this.location + "/services/folders", aseRepository, new Stack<String>() ); } } } result = true; } } return result; } // numTimesRequested helps us keep track of how many times we've sent this // request. From outside this method, it should always be set to 0. private void sendCreateFolderPost(String name, int parentFolderId, String description, String contact, int numTimesRequested) throws ParserConfigurationException, SAXException, IOException, ASEClientException { if ( name != null ) { if ( description == null ) description = ""; if ( contact == null ) contact = ""; HttpPost request = new HttpPost( this.location + "/AddFolder.aspx?fid=" + parentFolderId ); logger.debug( "HTTP Request: " + request.toString() ); BasicNameValuePair[] params = { new BasicNameValuePair( "__LASTFOCUS", "" ), new BasicNameValuePair( "__EVENTTARGET", "ctl00$ctl00$ctl00$MCH$RCH$SaveApplyCancel$SaveButton" ), new BasicNameValuePair( "__EVENTARGUMENT", "" ), new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$PageContentPlaceHolder$Identification$Name", name ), new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$PageContentPlaceHolder$Identification$Description", description ), new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$PageContentPlaceHolder$Identification$Contact", contact ), new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$ScrollerLeft=ScrollerLeft", "ScrollerLeft" ), new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$ScrollerTop=ScrollerTop", "ScrollerTop" ), new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$SaveApplyCancel$SaveButton", "Create" ), }; UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity( Arrays.asList( params ) ); // urlEncodedFormEntity.setContentEncoding(HTTP.UTF_8); request.setEntity( urlEncodedFormEntity ); request.addHeader( "User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0"); request.addHeader( "Referer", this.location + "/AddFolder.aspx?fid=" + parentFolderId); Header[] headers = request.getAllHeaders(); logger.debug( "Request Headers:" ); for ( int p = 0; p < headers.length; p++ ) { logger.debug( headers[p].toString() ); } logger.debug( "End of headers!" ); HttpResponse response = null; // Make the request response = send( request, httpContext ); // response = httpClient.execute( request, httpContext ); logger.debug( response.getStatusLine().toString() ); logger.debug( "HTTP Response: " + response.getStatusLine() ); // Read the full response and drop it. HttpEntity responseEntity = response.getEntity(); InputStream responseIn = responseEntity.getContent(); logger.debug( "Response Content: " ); BufferedReader reader = new BufferedReader( new InputStreamReader( responseIn ) ); String line; while ( ( line = reader.readLine() ) != null ) { logger.debug( line ); } EntityUtils.consume( responseEntity ); // Let the caller know if it succeeded int responseCode = response.getStatusLine().getStatusCode(); logger.debug( "Response Code: " + responseCode ); headers = response.getAllHeaders(); logger.debug( "Response Headers:" ); for ( int p = 0; p < headers.length; p++ ) { logger.debug( headers[p].toString() ); } logger.debug( "End of headers!" ); if (responseCode < 200 || responseCode >= 400) throw new ASEClientException("Failed to create folder on the ASE instance with response code " + responseCode); if ( responseCode != 200 ) { // This is to get around the check browser issue. ASE often // responds with a 302 redirect to CheckBrowserVersion.aspx on // the first request // But the second request goes through.. if ( responseCode == 302 ) { boolean incompatibleBrowser = false; for ( int p = 0; p < headers.length; p++ ) { if ( headers[p].getValue().contains( "CheckBrowserVersion.aspx" ) ) { logger.debug( "We're being redirected to due to an incompatible browser.. " ); incompatibleBrowser = true; // This check makes sure we only re-send the request // once... if ( numTimesRequested == 0 ) { // This should eventually be changed, since it's // really inefficient to re-run this entire // method.. we should just make the request and // parse the response. sendCreateFolderPost( name, parentFolderId, description, contact, numTimesRequested++ ); logger.debug( "We'll try the request one more time.." ); } else { logger.debug( "Already tried to request twice, so we won't do it again.." ); } } } if ( !incompatibleBrowser ) logger.debug( "We're being redirected, but not due to an incompatible brower.. this is probably normal." ); } else throw new ASEClientException( "Failed to create folder on the ASE instance with response code " + responseCode ); } } } public boolean viewFolder(String folder) throws ParserConfigurationException, SAXException, IOException, ASEClientException { boolean result = false; if ( folder != null && folder.length() > 0 ) { folder.replaceAll( "\\\\", "/" ); folder = TextUtil.trim( folder, "/" ); String[] pathElements = folder.split( "/" ); if ( pathElements.length > 1 ) { ASERepository aseRepository = new ASERepository(); mapFolders( this.location + "/services/folders", aseRepository, new Stack<String>() ); // Verify that pathElements[0].equals("ASE"); if ( pathElements[0].equals( "ASE" ) ) { Integer folderId = aseRepository.get( folder ); if ( folderId != null ) { logger.debug( "Getting ASE page for folder " + folder + " with folder id " + folderId ); sendViewFolderGet( folderId.intValue() ); } else logger.debug( "ASE folder " + folder + " does not exist" ); result = true; } else { logger.debug( "Invalid ASE folder " + folder + " does not begin with \"ASE\\\"" ); } } } return result; } private void sendViewFolderGet(int folderId) throws ParserConfigurationException, SAXException, IOException, ASEClientException { HttpGet request = new HttpGet( this.location + "/FolderExplorer.aspx?fid=" + folderId ); HttpResponse response = null; // Make the request response = send( request, httpContext ); // response = httpClient.execute( request, httpContext ); logger.debug( response.getStatusLine().toString() ); // Read the full response and drop it. HttpEntity responseEntity = response.getEntity(); EntityUtils.consume( responseEntity ); // Let the caller know if it succeeded int responseCode = response.getStatusLine().getStatusCode(); if ( responseCode < 200 || responseCode >= 400 ) throw new ASEClientException( "Failed to get folder page on the ASE instance with response code " + responseCode ); } private HttpResponse getParentFolderPage(String url) { HttpResponse response = null; try { HttpUriRequest request = new HttpGet( url ); response = send( request, httpContext ); // response = httpClient.execute( request, httpContext ); } catch (IOException e) { e.printStackTrace(); } return response; } /** * Not neccessary for ASE 8.6, but it is nice to have just in case. * * @param response * @return */ private String findViewState(HttpResponse response) { String viewstate = null; if ( response != null ) { HttpEntity responseEntity = response.getEntity(); try { BufferedReader reader = new BufferedReader( new InputStreamReader( responseEntity.getContent() ) ); String line; while ( ( line = reader.readLine() ) != null ) { int idPos = line.lastIndexOf( "id=\"__VIEWSTATE\"" ); if ( idPos > 0 ) { // Pattern pattern = // Pattern.compile("<input.* id=\"__VIEWSTATE\".* value=\"(.*)\""); Pattern pattern = Pattern.compile( "id=\"__VIEWSTATE\".* value=\"(.*)\"" ); Matcher matcher = pattern.matcher( line ); if ( matcher.find() ) { viewstate = matcher.group( 1 ); logger.debug( "Found viewstate: " + viewstate ); } } } EntityUtils.consume( responseEntity ); } catch (IOException e) { e.printStackTrace(); } } return viewstate; } private void checkForError(Document doc) throws ASEClientException { Element rootElement = doc.getDocumentElement(); if ( "error".equalsIgnoreCase( rootElement.getTagName() ) ) { logger.debug( "*** Error Occurred ***" ); NodeList nodes = rootElement.getChildNodes(); for ( int i = 0; i < nodes.getLength(); i++ ) { Node node = nodes.item( i ); String nodeName = node.getLocalName(); if ( "code".equalsIgnoreCase( nodeName ) || "message".equalsIgnoreCase( nodeName ) ) { logger.debug( node.getChildNodes().item( 0 ).getNodeValue() ); } else if ( "help".equalsIgnoreCase( nodeName ) ) { logger.debug( node.getAttributes().item( 0 ).getNodeValue() ); } } throw new ASEClientException( "Unexpected error." ); } } /** * Setup namespace context to be used by XPath expressions */ private static NamespaceContext _nsContext; static { _nsContext = new NamespaceContext() { public String getNamespaceURI(String prefix) { if ( prefix.equalsIgnoreCase( "ase" ) ) return "http://www.ibm.com/Rational/AppScanEnterprise"; return XMLConstants.NULL_NS_URI; } public String getPrefix(String arg0) { return null; } public Iterator<?> getPrefixes(String arg0) { return null; } }; } private static void print(Object message) { System.out.println( message ); } public void printDocument(Document doc) throws IOException, TransformerException { TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "no" ); transformer.setOutputProperty( OutputKeys.METHOD, "xml" ); transformer.setOutputProperty( OutputKeys.INDENT, "yes" ); transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" ); transformer.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "4" ); StreamResult result = new StreamResult( new StringWriter() ); transformer.transform( new DOMSource( doc ), result ); logger.info( result.getWriter().toString() ); } private static Map<String, String> parseOptions(String[] args) { Map<String, String> argsMap = new Hashtable<String, String>(); for ( int i = 0; i < args.length; i++ ) { if ( args[i].startsWith( "-" ) && args.length > i + 1 ) { argsMap.put( args[i].toLowerCase(), args[i + 1] ); } } return argsMap; } public static void main(String[] args) { Map<String, String> argsMap = parseOptions( args ); ASERestServicesClient app = null; if ( args.length == 0 ) { print( "java -jar ASEClient.jar -command getid -url <url> -folder <folder> -domain <domain> -user <username> -password <password> [-acceptssl true|false]" ); print( "java -jar ASEClient.jar -command create -url <url> -folder <folder> [-description <description>] [-contact <contact>] -domain <domain> -user <username> -password <password> [-acceptssl true|false]" ); print( "" ); print( "For test mode, the given URL maps to a test subdirectory of the current directory and all XML" ); print( "documents are named node.xml:" ); print( "java -jar ASEClient.jar -test true -command getid -url <url> -folder <folder>" ); print( "java -jar ASEClient.jar -test true -command create -url <url> -folder <folder> [-description <description>] [-contact <contact>]" ); } String folder = null; String description = null; String contact = null; // FIXME: Move testing out into a separate test class. // Handle connection related parameters if ( Boolean.valueOf( argsMap.get( "-test" ) ) ) { testMode = true; String location = argsMap.get( "-url" ); // app = new ASERestServicesClient( location ); } else { String location = argsMap.get( "-url" ); String domain = argsMap.get( "-domain" ); String user = argsMap.get( "-user" ); String password = argsMap.get( "-password" ); boolean acceptSsl = Boolean.valueOf( argsMap.get( "-acceptssl" ) ); app = new ASERestServicesClient( location, domain, user, password, acceptSsl ); } // Execute commands if ( argsMap.get( "-command" ).equals( "getid" ) ) { folder = argsMap.get( "-folder" ); Integer id = app.getFolderIdService( folder ); print( id ); } /* * else if (argsMap.get("-command").equals("create")) { folder = * argsMap.get("-folder"); description = argsMap.get("-description"); * contact = argsMap.get("-contact"); if * (app.createFolderService(folder, description, contact)) * print("Folder created"); else print("Folder not created"); } */ else System.err.println( "Illegal argument: " + args[0] ); } }